ci(object): make the ObjectHeader shape-descriptor census a real gate - #8110
Conversation
#8086 built the exact-callsite census #8067 asked for — the instrument that keeps `object_type`, `field_count` and `keys_array` retired as ShapeId takes over their facts — and then wired it into nothing. `grep -rn shape_descriptor_census` over every workflow, script and doc returns only the script naming its own baseline, so it has never been able to fail a build. Add it to `lint` alongside the other fifteen audits. Wiring it up exposed that one of its two arms was vacuous. The emitted-guard check rejected `add(..., "0"|"12"|"16")`, but all four functions in its list — `emit_class_field_loop_preheader_check`, `emit_proven_shape_recheck`, `emit_class_field_inline_precheck` and `emit_element_shape_field_load` — build their header address with `blk.gep(I8, &p, &[(I64, "N")])`. The pattern therefore matched a syntax those functions never emit. Planting `gep(I8, &elem_ptr, &[(I64, "16")])` in `emit_element_shape_field_load` — a read of the `keys_array` offset #8047 removes — left the census green. Match the gep form too. Sabotage-verified, each reverted after: * new `(*obj).field_count` read in `object/spill.rs` -> exit 1 * keys-pointer token in `proxy/put_value.rs::dyn_ic_try_store` -> exit 1 * offset 16 in `emit_element_shape_field_load` -> exit 1 (was 0) * offset 12 in the three `class_field_inline_guard` emitters -> exit 1 each Clean tree green before and after every one. No behaviour change; the census reports the same summary it did on 12f758a. Refs #8047, #8067, #8086.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe PR updates the shape-descriptor census to recognize current descriptor and emitter forms, refreshes its baseline, and runs it as a cancellation-safe required lint step. ChangesShape descriptor census
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested labels: ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Verified and endorsed — and this is my miss to own. I audited and merged #8086. My checks were: all four lint gates on a landing-equivalent tree, Confirmed independently on The script is referenced by exactly one file — itself. #8067's acceptance asked for a census; #8086 built it and wired it into nothing, so it has never gated anything and never could. And the arm that would have mattered was vacuous. Your finding that it matched The generalisable lesson I am taking from this, since it is a gap in how I audit: when a PR adds a gate, checker or census, verifying its tests pass is not enough — verify it is wired into a workflow, and verify it can go red. "Does this new gate run?" and "can it fail?" are two separate questions and I asked neither. Six sabotages with the clean tree re-verified green after each, and all 16 Two things from your report worth carrying into #8112/#8113 rather than losing here: |
Wiring the census into `lint` exposed two things it could not survive. The authority-surface check hardcoded `descriptors: HashMap<u32, ShapeDescriptor>`. #8157 changed that field to `crate::fast_hash::PtrHashMap` (SipHash on a bare u32 was 25% of self time in `shapes`), so the check failed on a rename it should not care about. The fact it asserts is that a by-id table EXISTS — the pattern now accepts an optional path qualifier and either hasher, and a real sabotage (swapping the field to `Vec<ShapeDescriptor>`) still trips it. The exact-callsite baseline is refreshed for six sites added since it was written: five in `param_type_guard.rs` from #8165's class-typed parameter guards, one in `process/node_module/source_map.rs`. They are raw reads of the three header words #8047/#8113 are trying to retire — recorded rather than hidden, so the migration list stays honest.
…eys_array sites The rebase onto PerryTS#8110 (census as a real gate) and PerryTS#8157 (PtrHashMap shape probes) brought two new `keys_array` callsites that postdate this branch's baseline: `param_type_guard.rs` (PerryTS#8094) and `process/node_module/source_map.rs` (PerryTS#7312). Both are ordinary uses of a field this change keeps; neither reintroduces `object_type` or `field_count`. The addr-class ratchet baseline is restored to main's verbatim: this branch produces the same verdict main does (542 held sites, the same two pre-existing stale entries), so the branch-local regeneration only served to drop PerryTS#7272's provenance comment. Claude-Session: https://claude.ai/code/session_01AHvBYz7E6wWKv8kmvLLGpj
…oxy.rs attribution Re-measured against `3be2016c1` (after PerryTS#8157's PtrHashMap shape probes and PerryTS#8110's census gate), quiet M1 mini, best-of-5, instructions AND peak RSS. The PerryTS#8157 hypothesis is REFUTED: 0 of 19 rows are faster and 12 pay more than 1%. The regression is slightly worse than the pre-PerryTS#8157 table on most rows (deeplist +8.20 -> +9.03, retain1 +7.99 -> +8.24, churn +1.76 -> +3.08); only shapes improves. That is what the arm-C partition predicts -- the dominant rows are footprint-coupled, and a cheaper probe cannot recover a cost that is not probing. New: PerryTS#8094 landed after this branch's original base and read both deleted words in param_type_guard::plain_object, so the rebase converts two free u32 loads into two descriptor probes on a path that is the PerryTS#2 self-time symbol on interp. interp/iso_miss/pipeline go from ~+0.3% to +3.35/+2.86/+4.33%. asyncpipe's +2.90% peak RSS is arena block quantization, not a footprint regression: it is exactly one 1 MB block, it flips sign with the nursery cap, and the shrunk arm holds strictly less live data. The proxy.rs:1523 attribution carried by the previous revision is withdrawn. Claude-Session: https://claude.ai/code/session_01AHvBYz7E6wWKv8kmvLLGpj
What
Wires
scripts/shape_descriptor_census.pyinto thelintjob, and fixes avacuous arm inside it that the wiring exposed.
Why
#8086 shipped the exact-callsite census that #8067 required — the instrument
that keeps
ObjectHeader::object_type,field_countandkeys_arrayretiredwhile
ShapeIdtakes over their facts, and the thing #8047 says must existbefore anyone touches the layout. It is a good instrument: it strips comments
and string literals, classifies every site
declarationvsaccess, diffs thefull multiset against a reviewed baseline (so it can tell an
ObjectHeaderreadfrom another struct's identically-named field), and carries its own lexer
self-test plus sabotage self-tests.
It is invoked by nothing:
The only hit is the script naming its own baseline.
lintruns fifteen otherpython audits; this is not one of them.
The vacuous arm
Wiring it up and sabotage-testing it found that the emitted-guard check could
not fail for any function it names. It matched
but all four functions in that list build their header address with
blk.gep(I8, &p, &[(I64, "N")]), notadd(..):emit_class_field_loop_preheader_checkblk.gep(I8, &obj_ptr, &[(I64, "4")])emit_proven_shape_recheckblk.gep(I8, &obj_ptr, &[(I64, "8")])emit_class_field_inline_precheckblk.gep(I8, &obj_ptr, &[(I64, "4")])emit_element_shape_field_loadblk.gep(I8, &elem_ptr, &[(I64, "8")])So the pattern targeted a syntax none of them uses. Planting a read of the
keys_arrayoffset that #8047 removes —gep(I8, &elem_ptr, &[(I64, "16")])inemit_element_shape_field_load— leftthe census green. This patch matches the gep form as well.
The two write-PIC checks below it are unaffected:
lower_generic_property_getand the
proxy_reflectemitters genuinely do usectx.block().add(I64, .., "N"),and their own regexes already spell that.
Verification
Sabotage-verified, every change reverted afterwards, clean tree green before and
after each:
(*obj).field_countread inobject/spill.rsproxy/put_value.rs::dyn_ic_try_storeemit_element_shape_field_loademit_class_field_loop_preheader_checkemit_proven_shape_recheckemit_class_field_inline_precheckAll sixteen
lintsteps, enumerated from.github/workflows/test.ymlratherthan from memory, pass on this branch:
cargo fmt --all -- --check,check_file_size.sh,workspace_architecture.py(--self-testand--check),local_binding_type_audit.py,gc_store_site_inventory.py,addr_class_inventory.py,class_id_collisions.py,gc_pin_sites.py,gc_runtime_root_holders.py,check_gc_env_knobs.py,check_llvm_corpus_currency.py,check_gc_doc_claims.py,raw_handle_debt.py,check_test_registration.py, and the newly wired census.No behaviour change: the census reports the same summary on this branch as on
12f758a22.Note this is a new gate, so per CLAUDE.md it has never been green — run it
once on
mainbefore promoting it to a required context.Not in this PR
The #8047 layout change itself. A full re-audit on
12f758a22found theprerequisite still incomplete; findings are posted on #8047.
Refs #8047, #8067, #8086.
Summary by CodeRabbit
Bug Fixes
Tests